<?php

/**
 * ExtWidget class file
 *
 * @author TrueSoftware B.V. (The Netherlands)
 * @link http://www.ext4yii.com
 * @copyright Copyright 2007-2010 TrueSoftwareB.V. all rights reserved.
 * @license http://www.ext4yii.com/license/
 *          EVALUATION VERSION - NOT FOR COMMERCIAL USE OR DISTRIBUTION.
 * @version 0.7.2
 */
class ExtWidget extends CWidget
{
    /*
     * Tag is used to host extra information in the the need to pass data
     * between the template parser down/up to other controls
     */

    public $tag;
    public $parent;
    public $configmap = array();
    public $jsconfigs = array();
    public $proptags = array();
    public $eventtable = array('sysnotification' => array('sender', 'e'));
    public $addedPlugins = array();
    public $parsed = false;
    public $differed_type = false;
    private $_config_class_types = array('ExtFunctionItem', 'ExtConfigItem');
    private $_toolip_class_types = array('ExtQuickTip');
    private $_xtemplate_class_types = array('ExtXTemplate');
    private $_menu_class_types = array('ExtMenu');
    private $_configs = array();
    private $_controller = null;
    protected $_xtype;
    protected $_xclass;
    private $_id;
    private static $_counter = 0;
    protected $astwalker = null;    
    private $_jsimply_ = 'js://';
    private $_virtualid = false;

    public function getVirtualId()
    {
        return $this->_virtualid;
    }

    public function setVirtualId($value)
    {
        $this->_virtualid = $value;
    }


    /*
     * This function is used to emit the correct div elements
     * when a control is nested within an ext:Panel.
     * This function is mainly called from hasHtmlChildren in ExtPanelBase
     */

    public function isHTMLElement()
    {
        return false;
    }

    protected function JSImply($data)
    {
        return $this->_jsimply_ . str_replace($this->_jsimply_, '', $data);
    }

    public function setAstWalker($walker)
    {
        $this->astwalker = $walker;
    }

    public function supportsProperty($name)
    {
        $name = str_replace('prop:', '', strtolower($name));
        return isset($this->proptags[$name]);
    }

    public function getController()
    {
        return $this->_controller;
    }

    public function setController($value)
    {
        $this->_controller = $value;
    }

    public function getXType()
    {
        return $this->_xtype;
    }

    public function setXtype($value)
    {
        $this->_xtype = $value;
    }

    public function setXClass($value)
    {
        $this->_xclass = $value;
    }

    public function getXClass()
    {
        return $this->_xclass;
    }

    public function createObjectConfig($name, $value)
    {
        if (is_array($value))
        {
            $result = array();
            foreach ($value as $k => $item)
            {
                $result[] = $k . ':' . $this->parseValue($item, $k);
            }
            return '{' . implode(',', $result) . '}';
        }
        else
        {
            return $name . ': {' . implode(',', $result) . '}';
        }
    }

    public function setObjectConfig($name, $value)
    {
        if (is_array($value))
        {
            $result = array();
            foreach ($value as $k => $item)
            {
                $result[] = $k . ':' . $this->parseValue($item, $k);
            }
            $this->setJavaScriptConfig($name, '{' . implode(',', $result) . '}');
        }
        else
        {
            $this->setJavaScriptConfig($name, '{' . $value . '}');
        }
    }

    public function setArrayConfig($name, $value)
    {
        $this->setJavaScriptConfig($name, '[' . implode(',', $value) . ']');
    }

    public function setJavaScriptConfig($name, $value)
    {
        $this->setConfig($name, $this->JSImply($value));
    }

    public function setConfig($name, $value)
    {
        if (!is_null($value))
            $this->_configs[$name] = $value;
    }

    public function getConfig($name)
    {
        if (isset($this->_configs[$name]))
        {
            $value = $this->_configs[$name];
            if (strtolower($value) == 'true' || strtolower($value) == 'false')
                return $value == 'true';
            else if (is_numeric($value))
                return $value + 0;
            else
                return $value;
        }
        else
            return null;
    }

    public function getAllConfigs()
    {
        return $this->_configs;
    }

    public function getAllMappings()
    {
        return $this->configmap;
    }


    public function deleteAllConfigs()
    {
        return $this->_configs = array();
    }

    public function mergeConfigs($data)
    {
        $this->_configs = array_merge($this->_configs, $data);
    }

    public function deleteConfig($name)
    {
        if (isset($this->_configs[$name]))
            unset($this->_configs[$name]);
    }

    public function mergeMappings($data)
    {
        $this->configmap = array_merge($this->configmap, $data);
    }

    protected function implodeConfigs()
    {
        $result = array();
        foreach ($this->_configs as $name => $value)
        {
            $m_name = $this->getMappedConfigName($name);
            $result[] = $m_name . ':' . $this->parseValue($value, $name);
        }
        return implode(',', $result);
    }

    public function parseValue($value, $name, $returnAsIs=false)
    {
        if (is_numeric($value))
        {
            return $value + 0;
        }
        else if (is_bool($value))
        {
            return $value ? 'true' : 'false';
        }
        else if (is_string($value))
        {
            $t = trim(strtolower($value));
            if ($t == 'true' || $t == 'false')
            {
                return $this->parseValue($t == 'true', $name);
            }
            else if (isset($this->jsconfigs[$this->getMappedConfigName($name)]))
            {
                return $value;
            }
            else
            {
                $test = explode($this->_jsimply_, '+' . $value);
                if (count($test) >= 2 && ($test[0] == '+'))
                    return preg_replace('/js:\/\//si', '', $value, 1);
                else
                {
                    if ($returnAsIs)
                        return $value;

                    $value = str_replace("\n", '\n', addslashes($value));
                    return '"' . str_replace("\r", '\r', $value) . '"';
                }
            }
        }
        else
            return $value;
    }

    /**
     * Removes the script tags. This function is used for handler properties
     * to fore NB to keep the code formatting
     * @param String $text
     * @return String
     */
    protected function removeScriptTags($text)
    {
        return str_ireplace('</script>', '', str_ireplace('<script>', '', $text));
    }

    protected function getFunctionArgsByEventName($eventName, $asarray=false, $jsimply=false,$mergeParams=array())
    {
        if (isset($this->eventtable[$eventName]))
            if ($asarray)
            {
                if ($jsimply)
                {
                    $t = array();
                    foreach ($this->eventtable[$eventName] as $key => $value)
                        $t[$key] = $this->JSImply($value);
                    return array_merge($t,$mergeParams);
                }
                else
                {
                    return array_merge($this->eventtable[$eventName],$mergeParams);
                }
            }
            else
                return implode(',', $this->eventtable[$eventName]);
        else
            throw new Exception(Ext4Yii::t('ext4yii', '{class} doens not support the {event} event.', array('{class}' => get_class($this), '{event}' => $eventName)));
    }

    public function parseNode(ExtHTMLParserNode $node, ExtASTWalker $astwalker)
    {
        $items = $this->parseConfigs($node, $astwalker);
        if (!is_null($items))
        {
            foreach ($items as $config)
                $this->setConfig($config['name'], $config['value']);
        }
    }

    protected function parseConfigs($node, $astwalker)
    {
        $configs = $node->findPropertyByName('configs');
        if (!is_null($configs))
        {
            $items = $this->parseNodeWidthTag($configs,
                            $astwalker,
                            true,
                            null,
                            $this,
                            $this->_config_class_types);
            $configs->shiftUp();
            return $items;
        }
    }

    public function parseNodeWidthTag($node, $astwalker, $shiftup, $options, $parentobject, $allowdTypes=null)
    {
        if (is_null($node))
            return null;

        $items = array();
        if (is_null($options))
            $options = array();
        $options['virtualParent'] = $node->exttag;
        $walker = new ExtASTWalker($node, $options, $astwalker->getController());
        $walker->walk($node, $parentobject, $allowdTypes);
        $items = $walker->objects;
        if ($shiftup)
            $node->shiftUp();

        return $items;
    }

    protected static function checkSetAjaxRequestValue($extSetter, $data, $owner=null)
    {
        if (Yii::app()->getRequest()->getIsAjaxRequest())
        {
            $newdata = array();
            foreach ($data as $i)
            {
                if (is_bool($i))
                    $newdata[] = $i ? 'true' : 'false';
                else
                    $newdata[] = $i;
            }

            if (is_null($owner))
                $owner = 'this';
            else
                $owner = sprintf('Ext.getCmp("%s")', $owner);
            $this->getController()->addClientAction(
                    vsprintf($owner . '.' . $extSetter, $newdata), 'a');
            return true;
        }
        else
            return false;
    }

    protected function parseContextMenu($node, $astwalker, $propName='contextmenu', $configName='contextMenu', $eventName='contextmenu')
    {
        $elemctx = true;
        $menus = $node->findPropertyByName($propName);
        if (!is_null($menus))
        {
            $items = $this->parseNodeWidthTag($menus,
                            $astwalker,
                            true,
                            array('asobject' => false),
                            $this,
                            array('ExtContextMenu'));

            /*
             * special handling the contextmenu for controls which have their own
             * contextmenu. In this case the context menu is not bound.
             */
            if (isset($this->eventtable[$eventName]))
                $elemctx = false;

            if (!is_null($items))
            {
                $configName = is_null($items[0]['id']) ? $configName : $items[0]['id'];
                $this->setJavaScriptConfig($configName, $items[0]['config']);
                $this->getController()->addAfterInitScript(
                        sprintf('ext4yii.initContextMenu("%s","%s","%s",%s)',
                                $this->getId(),
                                $configName,
                                $eventName,
                                $elemctx ? 'true' : 'false'
                        )
                );
            }
            $menus->shiftUp();
        }
    }

    protected function parseMenu($node, $astwalker, $configName='menu', $asobject=true, $propertyname='menu')
    {
        $menu = $node->findPropertyByName($propertyname);
        if (!is_null($menu))
        {
            $items = $this->parseNodeWidthTag($menu,
                            $astwalker,
                            true,
                            array('asobject' => false),
                            $this);
            if ($configName == 'items')
                $this->setJavsScriptConfig($configName, $items[0]);
            else
                $this->setConfig($configName, $this->JSImply($items[0]));
            $menu->shiftUp();
        }
    }

    /**
     * Parses an script inner property
     * @param string $propertyName
     * @param string $configName
     * @param ExtHTMLDomNode $node
     * @param ExtASTWalker $astwalker
     */
    protected function parseScriptProperty($propertyName, $configName, $node, $astwalker, $wrapin=null)
    {
        $property = $node->findPropertyByName($propertyName);
        if (!is_null($property))
        {
            $script = trim($this->removeScriptTags($property->innertext()));
            if (!is_null($configName))
            {
                if (!is_null($wrapin))
                    $script = sprintf($wrapin, $script);

                if (isset($this->jsconfigs[$configName]))
                    $this->setConfig($configName, $script);
                else
                    $this->setJavaScriptConfig($configName, $script);
            }

            $property->innertext = '';
            $property->shiftUp();
            return $script;
        }
        return null;
    }

    protected function parseQuickTip($node, $astwalker, $config, $asobject)
    {
        $tip = $node->findPropertyByName('tooltip');
        if (!is_null($tip))
        {
            $items = $this->parseNodeWidthTag($tip,
                            $astwalker,
                            true,
                            array('asobject' => $asobject),
                            $this,
                            $this->_toolip_class_types);
            $this->addConfig($config, $items[0]);
            $tip->shiftUp();
        }
    }

    protected function parseXTemplate($propertyName, $configName, $node, $astwalker)
    {
        $prop = $node->findPropertyByName(strtolower($propertyName));
        if (!is_null($prop))
        {
            $items = $this->parseNodeWidthTag($prop,
                            $astwalker,
                            true,
                            array('asobject' => true),
                            $this,
                            $this->_xtemplate_class_types);

            // if an xtemplate exists
            if (count($items) != 0)
            {
                $this->setJavaScriptConfig($configName, $items[0]);
                return $items[0];
            }
            else
            {
                $result = trim($this->removeScriptTags($prop->innertext()));
                $this->setConfig($configName, $result);
                return $result;
            }
            $prop->shiftUp();
            return true;
        }
        else
        {
            return null;
        }
    }

    protected function parseParameters($node, $astwalker, $propertyName, $configName, $type=null)
    {
        $options = array('asobject' => false);
        $params = $node->findPropertyByName($propertyName);
        $result = array();
        
        if (!is_null($params))
        {
            $items = $this->parseNodeWidthTag($params,
                            $astwalker,
                            true,
                            array('asobject' => false),
                            $this,
                            $type);

            if (!is_null($items))
            {                
                foreach ($items as $param)
                    $result = array_merge($result, $param);

                if(!is_null($configName))
                    $this->setObjectConfig($configName, $result);
            }
            $params->shiftUp();
        }

        return $result;
    }

    /**
     * This function is used to configure the ID that is generated/set for the
     * rendered component.
     * NOTE: not all components like tree node and menu items need their ID be
     * registered as global.
     * @param <bool> $registerID
     * @param <bool> $makeGlobal
     */
    protected function configureClientIDs($registerID=true, $makeGlobal=true)
    {
        if ($registerID)
            $this->setConfig('id', $this->getID());

        if ($makeGlobal)
            $this->makeIDGlobal();
    }

    public function renderConfig($asobject=true)
    {
        $this->configureClientIDs();
        $this->evaluateValues();

        if ($this instanceof ExtGeneric)
        {
            return $this->renderGeneric($asobject);
        }
        else
        {
            if ($asobject)
            {
                return 'new ' . $this->getXClass() . '({' . $this->implodeConfigs() . '})';
            }
            else
            {
                if (!$this->astwalker->getOption('disableXType', false))
                    $this->setConfig('xtype', $this->getXType());
                return '{' . $this->implodeConfigs() . '}';
            }
        }
    }

    private function makeIDGlobal()
    {
        $id = $this->getId();
        if ($id != "" && stristr('!+' . $id, '!+x4y') === false && $this->getVirtualID() === false)
        {
            $this->getController()->createGlobalID($this);
        }
    }

    private function checkAndMakeComplexJavaScriptValue($value, $ival)
    {
        $check = explode(':', $value);
        /* check if the js value was not embeded inside another javascript code (eg foo([1,2,3])) */
        if (isset($check[1]) && (trim(str_replace($ival, '', $check[1]))) == '""')
            return $check[0] . ':' . $ival;
        else
            return $value;
    }

    protected function evaluateValues()
    {
        foreach ($this->_configs as $key => $value)
            $this->setConfig($key, $this->evaluateValue($value));
    }

    public static function resetId()
    {
        self::$_counter = 0;
    }

    public function setId($value)
    {
        $this->_id = $value;
    }

    public function getId($autoGenerate=true)
    {
        $result = null;
        if ($this->_id !== null)
            $result = $this->_id;
        else if ($autoGenerate)
            $result = $this->_id = 'x4y' . self::$_counter++;

        return $this->evaluateValue($result);
    }

    public function evaluateValue($value)
    {
        $rule = '<%=?\s*(.*?)\s*%>';
        $n = preg_match_all('/' . $rule . '/msS', $value, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
        for ($i = 0; $i < $n; ++$i)
        {
            ob_start();
            $match = &$matches[$i];
            $str = $match[0][0];
            if (strpos($str, '<%=') === 0) // expression
                $this->runCode('echo ' . $match[1][0]);
            else if (strpos($str, '<%') === 0) // statement
                $this->runCode($match[1][0]);
            $value = $this->evaluateValue(str_replace($str, ob_get_contents(), $value));
            ob_clean();
        }
        return $value;
    }

    private function runCode($code)
    {
        eval($code . ';');
    }

    public function getMappedConfigName($name)
    {
        if (isset($this->configmap[$name]))
            return $this->configmap[$name];
        else
            return $name;
    }

    private function renderToContainer($node)
    {
        $cid = $this->getId() . '_container';
        $this->setConfig('renderTo', $cid);
        $node->tag = 'div';
        $node->attr = array(); // clear the old attrs
        $node->setAttribute('id', $cid);
    }

    public function createContainer(ExtHTMLParserNode $node)
    {
        if (is_null($this->parent))
        {
            $this->renderToContainer($node);
        }
        else
        {
            if ($this->parent instanceof ExtPanel)
            {
                if ($node->virtualParent == 'body' && $this->parent->hasHtmlElements)
                {
                    $this->renderToContainer($node);
                }
                else
                    $node->shiftUp();
            }
            else
                $node->shiftUp();
        }
    }

}

?>
